package smartgrid.agents.prosumer.social;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import smartgrid.agents.prosumer.IProsumerAgent;
import smartgrid.agents.prosumer.ProsumerAbstractAgent;
import smartgrid.conf.ProsumerConf;
import smartgrid.modules.normative.norm.Norm;
import smartgrid.modules.normative.salience.NormSalience;
import smartgrid.modules.normative.salience.NormSalience.Status;
import smartgrid.modules.normative.sanction.DenounceSanction;
import smartgrid.modules.normative.sanction.InformationSanction;
import smartgrid.modules.normative.sanction.PunishmentInformationSanction;
import smartgrid.modules.normative.sanction.PunishmentSanction;
import smartgrid.modules.normative.sanction.ReputationSanction;
import smartgrid.modules.normative.sanction.SanctionAbstract;
import smartgrid.modules.normative.sanction.SuspensionSanction;
import smartgrid.modules.reputation.ImageReputation;
import smartgrid.modules.trade.Contract;
import smartgrid.modules.trade.Demand;
import smartgrid.modules.trade.Offer;
import smartgrid.output.AbstractEntity;
import smartgrid.output.ContractOutputEntity;
import smartgrid.output.OutputController;
import smartgrid.output.AbstractEntity.EntityType;
import smartgrid.output.ProsumerSocialOutputEntity;
import smartgrid.output.ProsumerSocialOutputEntity.Field;
import smartgrid.util.random.RandomUtil;
import smartgrid.util.sort.Sort;

public class ProsumerSocialAgent extends ProsumerAbstractAgent {
	
	private static final Logger					logger	= LoggerFactory
																									.getLogger(ProsumerSocialAgent.class);
	
	private double											COST_DENOUNCE;
	
	private int													NORM_ACTIVATE_NUM_MSG;
	
	private int													NORM_ACTIVATE_NUM_ACTION;
	
	private double											NORM_ACTIVATE_SAL_THRESHOLD;
	
	private double											REPUTATION_WEIGHT;
	
	private double											INDIVIDUAL_WEIGHT;
	
	private double											NORMATIVE_WEIGHT;
	
	private double											DEMAND_DW;
	
	private double											DEMAND_RW;
	
	private double											OSTRACIZE_THRESHOLD;
	
	private int													INFLUENCE_RADIUS;
	
	private double											INFLUENCE_THRESHOLD;
	
	private NormSalience								normSalience;
	
	private int													normativeMsg;
	
	private int													normativeAction;
	
	private ImageReputation							reputation;
	
	private double											totalSanctions;
	
	private double											sanctionCost;
	
	private int[][]											sanctioning;
	
	private Map<Integer, Integer>				pInteraction;
	
	private Map<Integer, Integer>				nInteraction;
	
	// ProsumerId, SanctionType
	private Map<Integer, Integer>				lastSanction;
	
	// SanctionType, Number Effective
	private Map<Integer, Integer>				pEffective;
	
	// SanctionType, Number Not Effective
	private Map<Integer, Integer>				nEffective;
	
	private Map<Integer, List<Integer>>	influential;
	
	private int													suspension;
	
	private List<Integer>								ostracized;
	
	private List<Integer>								obsReputation;
	
	
	/**
	 * Constructor
	 * 
	 * @param id
	 *          Prosumer identification
	 * @param conf
	 *          Prosumer configuration
	 * @return none
	 */
	public ProsumerSocialAgent(Integer id, ProsumerConf conf,
			String prosumerFilename) {
		super(id, conf);
		
		this.COST_DENOUNCE = conf.getProsumerSocialConf().getCostToDenounce();
		this.NORM_ACTIVATE_NUM_MSG = conf.getProsumerSocialConf()
				.getNormActivateNumMsg();
		this.NORM_ACTIVATE_NUM_ACTION = conf.getProsumerSocialConf()
				.getNormActivateNumAction();
		this.NORM_ACTIVATE_SAL_THRESHOLD = conf.getProsumerSocialConf()
				.getNormActivateSalThreshold();
		this.INDIVIDUAL_WEIGHT = conf.getProsumerSocialConf().getIndividualWeight();
		this.NORMATIVE_WEIGHT = conf.getProsumerSocialConf().getNormativeWeight();
		this.REPUTATION_WEIGHT = conf.getProsumerSocialConf().getReputationWeight();
		this.DEMAND_DW = conf.getProsumerSocialConf().getDemandDW();
		this.DEMAND_RW = conf.getProsumerSocialConf().getDemandRW();
		this.OSTRACIZE_THRESHOLD = conf.getProsumerSocialConf()
				.getOstracizeThreshold();
		this.INFLUENCE_RADIUS = conf.getProsumerSocialConf().getInfluenceRadius();
		this.INFLUENCE_THRESHOLD = conf.getProsumerSocialConf()
				.getInfluenceThreshold();
		
		this.selfInterest = conf.getSelfInterest();
		
		this.normSalience = new NormSalience(conf.getProsumerSocialConf()
				.getNormSalience());
		
		if (conf.getProsumerSocialConf().getNormActive()) {
			this.normSalience.setStatus(Status.ACTIVE);
		}
		
		this.reputation = new ImageReputation(conf.getProsumerSocialConf()
				.getImage(), conf.getProsumerSocialConf().getReputation(), conf
				.getProsumerSocialConf().getImageWeight(), (1 - conf
				.getProsumerSocialConf().getImageWeight()));
		
		// Sanction per timestep
		this.sanctioning = new int[SanctionAbstract.Enforcer.values().length][SanctionAbstract.Type
				.values().length];
		for(int i = 0; i < this.sanctioning.length; i++) {
			for(int j = 0; j < this.sanctioning[i].length; j++) {
				this.sanctioning[i][j] = 0;
			}
		}
		
		this.pInteraction = new HashMap<Integer, Integer>();
		this.nInteraction = new HashMap<Integer, Integer>();
		
		this.lastSanction = new HashMap<Integer, Integer>();
		this.pEffective = new HashMap<Integer, Integer>();
		this.nEffective = new HashMap<Integer, Integer>();
		for(SanctionAbstract.Type type : SanctionAbstract.Type.values()) {
			this.pEffective.put(type.ordinal(), 0);
			this.nEffective.put(type.ordinal(), 0);
		}
		
		this.influential = new HashMap<Integer, List<Integer>>();
		
		this.normativeMsg = 0;
		this.normativeAction = 0;
		this.totalSanctions = 0.0;
		this.sanctionCost = 0.0;
		this.suspension = 0;
		this.ostracized = new ArrayList<Integer>();
		this.obsReputation = new ArrayList<Integer>();
		
		OutputController.init(EntityType.SOCIAL_PROSUMER, prosumerFilename);
	}
	
	
	@Override
	public void setDistance(Map<Integer, Map<Integer, Double>> distances) {
		super.setDistance(distances);
		
		// Determines the influential agents considering a specific radius
		// (INFLUENCE_RADIUS)
		for(Integer prosumerId : distances.keySet()) {
			
			List<Integer> influenced = new ArrayList<Integer>();
			Map<Integer, Double> neighbors = distances.get(prosumerId);
			
			for(Integer neighborId : neighbors.keySet()) {
				double distance = neighbors.get(neighborId);
				if (distance <= this.INFLUENCE_RADIUS) {
					influenced.add(neighborId);
				}
			}
			
			this.influential.put(prosumerId, influenced);
		}
	}
	
	
	/**
	 * Calculate the social persuasion over another Prosumer
	 * 
	 * @param influencedId
	 *          Prosumer identification to be influenced
	 * @return Social influence over the influenced Prosumer
	 */
	private double socialInfluence(int influencedId) {
		List<Integer> persuaded = this.influential.get(influencedId);
		
		int numPersuaded = 0;
		int numInteractions = 0;
		double sumPersuasion = 0;
		for(Integer persuadedId : persuaded) {
			
			int pInt = 0;
			if (this.pInteraction.containsKey(persuadedId)) {
				pInt = this.pInteraction.get(persuadedId);
			}
			
			int nInt = 0;
			if (this.nInteraction.containsKey(persuadedId)) {
				nInt = this.nInteraction.get(persuadedId);
			}
			
			if ((pInt + nInt) > 0) {
				sumPersuasion += (double) pInt
						* ((double) 1.0 / (double) this.distances.get(persuadedId).get(
								influencedId));
				
				numInteractions += pInt + nInt;
				numPersuaded++;
			}
		}
		
		double pIndex = 0.0;
		if ((!persuaded.isEmpty()) && (numInteractions > 0)) {
			pIndex = ((double) numPersuaded / (double) persuaded.size())
					* ((double) sumPersuasion / (double) numInteractions);
		}
		
		return pIndex;
	}
	
	
	@Override
	public void initSim() {
		this.normSalience.setNumNeighbors(this.neighbors.size());
	}
	
	
	@Override
	public void initTimestep(int timestep) {
		super.initTimestep(timestep);
		
		this.totalSanctions = 0.0;
		this.sanctionCost = 0.0;
		this.obsReputation.clear();
		
		for(int i = 0; i < this.sanctioning.length; i++) {
			for(int j = 0; j < this.sanctioning[i].length; j++) {
				this.sanctioning[i][j] = 0;
			}
		}
		
		if (this.suspension > 0) {
			this.suspension--;
		}
	}
	
	
	/**
	 * Calculate the Individual Goal as a balance between the monetary gains and
	 * its own reputation. If the norm is active, the agent integrates the
	 * importance of the norm to the agent in the decision to how much offer
	 * 
	 * @param none
	 * @return Offer
	 */
	@Override
	public Offer getOffer() {
		if ((this.hasExcessEnergyEstimation()) && (this.suspension <= 0)) {
			
			double strategy = (this.INDIVIDUAL_WEIGHT * this.selfInterest)
					+ (this.REPUTATION_WEIGHT * this.reputation.getReputation(this.id));
			
			double normalizer = this.INDIVIDUAL_WEIGHT + this.REPUTATION_WEIGHT;
			
			if (this.normSalience.isActive()) {
				strategy += this.NORMATIVE_WEIGHT
						* (1 - this.normSalience.getSalience());
				normalizer += this.NORMATIVE_WEIGHT;
			}
			strategy = strategy / normalizer;
			
			double minEstimate = (this.generateEstimation * this.generateAccuracy)
					- (this.consumeEstimation * (1 + (1 - this.consumeAccuracy)));
			
			double maxEstimate = (this.generateEstimation * (1 + (1 - this.generateAccuracy)))
					- (this.consumeEstimation * this.consumeAccuracy);
			
			double offering = Math.max(0, minEstimate
					+ ((maxEstimate - minEstimate) * strategy));
			
			this.offer = new Offer(this.id, offering, this.sellingPrice,
					this.ostracized);
		}
		
		return this.offer;
	}
	
	
	/**
	 * Give a preference to the closest Prosumer offering the amount of energy
	 * necessary
	 * 
	 * @param offers
	 *          Energy offers
	 * @return Demands
	 */
	@Override
	public Demand getDemand(Map<Integer, Offer> offers) {
		if ((!this.hasExcessEnergyEstimation()) && (this.suspension <= 0)) {
			
			double neededEnergy = this.consumeEstimation - this.generateEstimation;
			
			// Calculate the farthest Prosumer
			Map<Integer, Double> prosumerDists = new HashMap<Integer, Double>();
			double maxHops = 0;
			for(Integer offerId : offers.keySet()) {
				Offer offer = offers.get(offerId);
				int prosumerId = offer.getProsumer();
				
				if ((!this.ostracized.contains(prosumerId))
						&& (offer.getQuantity() >= neededEnergy)) {
					
					double prosumerDist = 0;
					Map<Integer, Double> distances = this.distances.get(this.id);
					if (distances.containsKey(prosumerId)) {
						prosumerDist = distances.get(prosumerId);
						if (maxHops < prosumerDist) {
							maxHops = prosumerDist;
						}
					}
					prosumerDists.put(prosumerId, prosumerDist);
				}
			}
			
			Map<Integer, Double> unsortedOffers = new TreeMap<Integer, Double>();
			for(Integer prosumerId : prosumerDists.keySet()) {
				double prosumerDist = prosumerDists.get(prosumerId);
				
				double reputation = this.reputation.getEvaluation(prosumerId);
				
				double preference;
				if (prosumerDist != 0) {
					double dist = (double) prosumerDist / (double) maxHops;
					
					preference = (this.DEMAND_DW * dist)
							+ (this.DEMAND_RW * (1 - reputation));
				} else {
					preference = 1 - reputation;
				}
				unsortedOffers.put(prosumerId, preference);
			}
			
			Queue<Integer> prefOffers = new LinkedList<Integer>();
			for(Entry<Integer, Double> entry : Sort
					.entriesSortedByValues(unsortedOffers)) {
				prefOffers.add(entry.getKey());
			}
			
			this.demand = new Demand(this.id, neededEnergy, prefOffers);
		}
		
		return this.demand;
	}
	
	
	@Override
	public void evaluateContract() {
		if (this.contract != null) {
			
			int opponent;
			if (this.contract.getSupplier() == this.id) {
				opponent = this.contract.getBuyer();
			} else {
				opponent = this.contract.getSupplier();
			}
			
			boolean complied;
			if (Norm.evaluateContract(this.contract).equals(Norm.Status.COMPLIED)) {
				complied = true;
				
				int interactions = 0;
				if (this.pInteraction.containsKey(opponent)) {
					interactions = this.pInteraction.get(opponent);
				}
				this.pInteraction.put(opponent, (interactions + 1));
				
			} else {
				complied = false;
				
				int interactions = 0;
				if (this.nInteraction.containsKey(opponent)) {
					interactions = this.nInteraction.get(opponent);
				}
				this.nInteraction.put(opponent, (interactions + 1));
				
			}
			this.contract.setComplied(complied);
			
			// Update Effectiveness
			if (this.lastSanction.containsKey(opponent)) {
				int type = this.lastSanction.remove(opponent);
				
				if (complied) {
					this.pEffective.put(type, this.pEffective.get(type) + 1);
				} else {
					this.nEffective.put(type, this.nEffective.get(type) + 1);
				}
			}
			
		}
	}
	
	
	@Override
	public List<SanctionAbstract> decideSanction() {
		List<SanctionAbstract> sanctions = new ArrayList<SanctionAbstract>();
		
		if (this.contract != null) {
			
			if (this.contract.getBuyer() == this.id) {
				
				int opponent = this.contract.getSupplier();
				
				if (!this.contract.getComplied()) {
					
					this.reputation.addViolation(opponent);
					
					// MAGNITUDE
					double denominator = 1;
					double magnitude = Norm.getMagnitude(this.contract);
					
					// NORM SALIENCE
					double salience = 0.0;
					if (this.normSalience.isActive()) {
						salience = this.normSalience.getSalience();
						denominator++;
					}
					
					// FREQUENCY VIOLATION
					int nInt = 0;
					int pInt = 0;
					double freq = 0.0;
					if (this.pInteraction.containsKey(opponent)) {
						pInt = this.pInteraction.get(opponent);
					}
					
					if (this.nInteraction.containsKey(opponent)) {
						nInt = this.nInteraction.get(opponent);
					}
					
					int sumInt = pInt + nInt;
					if (sumInt > 0) {
						double x = ((double) nInt / (double) sumInt) * nInt;
						freq = (1.0 - Math.exp(-2 * x)) / (1.0 + Math.exp(-2 * x));
						denominator++;
					}
					
					// SANCTION PROBABILITY
					double sanctionProb = (magnitude + salience + freq) / denominator;
										
					// Send a Sanction depending on MAGNITUDE and SALIENCE
					if (RandomUtil.nextDouble() < sanctionProb) {
						
						// INFLUENCE
						double influence = this.socialInfluence(opponent);
						
						// EFFICACY
						int pSocial = this.pEffective.get(SanctionAbstract.Type.REPUTATION
								.ordinal());
						int nSocial = this.nEffective.get(SanctionAbstract.Type.REPUTATION
								.ordinal());
						int tSocial = pSocial + nSocial;
						double socialS = 0.0;
						if (tSocial > 0) {
							socialS = (double) pSocial / (double) tSocial;
						}
						
						int pInst = this.pEffective.get(SanctionAbstract.Type.DENOUNCE
								.ordinal());
						int nInst = this.nEffective.get(SanctionAbstract.Type.DENOUNCE
								.ordinal());
						int tInst = pInst + nInst;
						double instS = 0.0;
						if (tInst > 0) {
							instS = (double) pInst / (double) tInst;
						}
						
						// USE REPUTATION SPREADING AS REPUTATION
						if ((socialS > instS) || (influence > this.INFLUENCE_THRESHOLD)) {
							
							this.lastSanction.put(opponent,
									SanctionAbstract.Type.REPUTATION.ordinal());
							
							double evaluation = this.reputation.getEvaluation(opponent);
							
							List<Integer> persuaded = this.influential.get(opponent);
							for(Integer persuadedId : persuaded) {
								
								if (this.pInteraction.containsKey(persuadedId)) {
									int persuadedPI = this.pInteraction.get(persuadedId);
									
									if (persuadedPI > 0) {
										sanctions.add(new ReputationSanction(this.id, persuadedId,
												opponent, evaluation));
										
										this.sanctioning[SanctionAbstract.Enforcer.APPLIED
												.ordinal()][SanctionAbstract.Type.REPUTATION.ordinal()]++;
									}
								}
							}
							
							// DENOUNCE VIOLATION TO REGULAROTY AGENCY
						} else {
							
							this.lastSanction.put(opponent,
									SanctionAbstract.Type.DENOUNCE.ordinal());
							
							sanctions.add(new DenounceSanction(this.id, opponent, magnitude));
							
							this.sanctionCost += this.COST_DENOUNCE;
							this.wealth -= this.COST_DENOUNCE;
							
							this.sanctioning[SanctionAbstract.Enforcer.APPLIED.ordinal()][SanctionAbstract.Type.DENOUNCE
									.ordinal()]++;
						}
					}
					
					// Complied
				} else {
					
					this.reputation.addCompliance(opponent);
				}
			}
		}
		
		return sanctions;
	}
	
	
	@Override
	public void handleSanction(SanctionAbstract sanction) {
		
		double value = 0;
		if (sanction instanceof PunishmentSanction) {
			value = ((PunishmentSanction) sanction).getPunishment();
			
		} else if (sanction instanceof PunishmentInformationSanction) {
			value = ((PunishmentInformationSanction) sanction).getPunishment();
			
		} else if (sanction instanceof InformationSanction) {
			
		} else if (sanction instanceof ReputationSanction) {
			
			ReputationSanction rSanction = (ReputationSanction) sanction;
			this.reputation.updateReputation(rSanction.getSender(),
					rSanction.getTarget(), rSanction.getReputation());
			
			this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.REPUTATION
					.ordinal()]++;
			
		} else if (sanction instanceof SuspensionSanction) {
			
			SuspensionSanction sSanction = (SuspensionSanction) sanction;
			this.suspension = sSanction.getSuspensionPeriod();
			
		}
		this.totalSanctions += value;
		this.wealth -= value;
		
	}
	
	
	@Override
	public void updateStrategy(
			Map<Integer, List<SanctionAbstract>> sanctionsSender,
			Map<Integer, List<SanctionAbstract>> sanctionsReceiver) {
		
		int own = 0;
		int complied = 0;
		int violated = 0;
		int npv = 0;
		if (this.contract != null) {
			
			if (this.contract.getSupplier() == this.id) {
				
				if (this.contract.getComplied()) {
					own = 1;
					
				} else {
					own = -1;
					
					List<SanctionAbstract> sanctionList = sanctionsReceiver.get(this.id);
					
					if ((sanctionList != null) && (!sanctionList.isEmpty())) {
						
						for(SanctionAbstract sanction : sanctionList) {
							
							if (sanction instanceof PunishmentSanction) {
								this.sanctioning[SanctionAbstract.Enforcer.RECEIVED.ordinal()][SanctionAbstract.Type.PUNISHMENT
										.ordinal()]++;
								this.normativeAction++;
								
							} else if (sanction instanceof PunishmentInformationSanction) {
								this.sanctioning[SanctionAbstract.Enforcer.RECEIVED.ordinal()][SanctionAbstract.Type.PUNISHMENT_INFORMATION
										.ordinal()]++;
								this.normativeAction++;
								this.normativeMsg++;
								
							} else if (sanction instanceof InformationSanction) {
								this.sanctioning[SanctionAbstract.Enforcer.RECEIVED.ordinal()][SanctionAbstract.Type.INFORMATION
										.ordinal()]++;
								this.normativeMsg++;
								
							} else if (sanction instanceof SuspensionSanction) {
								this.sanctioning[SanctionAbstract.Enforcer.RECEIVED.ordinal()][SanctionAbstract.Type.SUSPENSION
										.ordinal()]++;
								this.normativeAction++;
								
							}
						}
						
					} else {
						npv++;
					}
				}
				
			} else if (this.contract.getBuyer() == this.id) {
				own = 0;
				if (this.contract.getComplied()) {
					complied++;
					this.normativeAction++;
					
					// Update reputation
					this.reputation.addCompliance(this.contract.getSupplier());
					
				} else {
					violated++;
					
					// Update reputation
					this.reputation.addViolation(this.contract.getSupplier());
				}
				
			}
		}
		
		List<Integer> analyzedContracts = new ArrayList<Integer>();
		Map<Integer, List<Integer>> exclude = new HashMap<Integer, List<Integer>>();
		
		for(IProsumerAgent neighbor : this.neighbors.get(this.id)) {
			Contract contract = neighbor.getContract();
			int neighborId = neighbor.getId();
			
			if ((contract != null) && (contract.getSupplier() != this.id)
					&& (contract.getBuyer() != this.id)
					&& (contract.getSupplier() == neighborId)
					&& (!analyzedContracts.contains(contract.getId()))) {
				
				if (contract.getComplied()) {
					complied++;
					this.normativeAction++;
					
					// Update reputation
					this.reputation.addCompliance(neighborId);
					
				} else {
					
					violated++;
					
					// Update reputation
					this.reputation.addViolation(neighborId);
					
					// Calculate the received Sanctions
					if (sanctionsReceiver.containsKey(neighborId)) {
						
						for(SanctionAbstract sanction : sanctionsReceiver.get(neighborId)) {
							
							List<Integer> excluded;
							if (exclude.containsKey(sanction.getReceiver())) {
								excluded = exclude.get(sanction.getReceiver());
							} else {
								excluded = new ArrayList<Integer>();
							}
							
							if (!excluded.contains(sanction.getSender())) {
								excluded.add(sanction.getSender());
								exclude.put(sanction.getReceiver(), excluded);
								
								if (sanction instanceof PunishmentSanction) {
									this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.PUNISHMENT
											.ordinal()]++;
									this.normativeAction++;
									
								} else if (sanction instanceof PunishmentInformationSanction) {
									this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.PUNISHMENT_INFORMATION
											.ordinal()]++;
									this.normativeAction++;
									this.normativeMsg++;
									
								} else if (sanction instanceof InformationSanction) {
									this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.INFORMATION
											.ordinal()]++;
									this.normativeMsg++;
									
								} else if (sanction instanceof SuspensionSanction) {
									this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.SUSPENSION
											.ordinal()]++;
									this.normativeAction++;
									
								} else if (sanction instanceof DenounceSanction) {
									this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.DENOUNCE
											.ordinal()]++;
									this.normativeAction++;
									
								} else if (sanction instanceof ReputationSanction) {
									
									ReputationSanction rep = (ReputationSanction) sanction;
									if (rep.getTarget() == this.id) {
										this.sanctioning[SanctionAbstract.Enforcer.OBSERVED
												.ordinal()][SanctionAbstract.Type.REPUTATION.ordinal()]++;
										this.reputation.updateReputation(rep.getSender(), this.id,
												rep.getReputation());
									}
									
								}
							}
						}
						
					} else {
						npv++;
					}
					
					// Calculate the sent Sanctions
					if (sanctionsSender.containsKey(neighborId)) {
						
						for(SanctionAbstract sanction : sanctionsSender.get(neighborId)) {
							
							List<Integer> excluded;
							if (exclude.containsKey(sanction.getReceiver())) {
								excluded = exclude.get(sanction.getReceiver());
							} else {
								excluded = new ArrayList<Integer>();
							}
							
							if (!excluded.contains(sanction.getSender())) {
								excluded.add(sanction.getSender());
								exclude.put(sanction.getReceiver(), excluded);
								
								if (sanction instanceof PunishmentSanction) {
									this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.PUNISHMENT
											.ordinal()]++;
									this.normativeAction++;
									
								} else if (sanction instanceof PunishmentInformationSanction) {
									this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.PUNISHMENT_INFORMATION
											.ordinal()]++;
									this.normativeAction++;
									this.normativeMsg++;
									
								} else if (sanction instanceof InformationSanction) {
									this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.INFORMATION
											.ordinal()]++;
									this.normativeMsg++;
									
								} else if (sanction instanceof SuspensionSanction) {
									this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.SUSPENSION
											.ordinal()]++;
									this.normativeAction++;
									
								} else if (sanction instanceof DenounceSanction) {
									this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.DENOUNCE
											.ordinal()]++;
									this.normativeAction++;
									
								}
							}
						}
					}
				}
				
				analyzedContracts.add(contract.getId());
			}
		}
		
		int pun = 0;
		for(SanctionAbstract.Enforcer enforcer : SanctionAbstract.Enforcer.values()) {
			pun += this.sanctioning[enforcer.ordinal()][SanctionAbstract.Type.PUNISHMENT
					.ordinal()]
					+ this.sanctioning[enforcer.ordinal()][SanctionAbstract.Type.SUSPENSION
							.ordinal()]
					+ this.sanctioning[enforcer.ordinal()][SanctionAbstract.Type.REPUTATION
							.ordinal()];
		}
		
		int sanc = 0;
		for(SanctionAbstract.Enforcer enforcer : SanctionAbstract.Enforcer.values()) {
			sanc += this.sanctioning[enforcer.ordinal()][SanctionAbstract.Type.PUNISHMENT_INFORMATION
					.ordinal()];
		}
		
		int invoke = 0;
		for(SanctionAbstract.Enforcer enforcer : SanctionAbstract.Enforcer.values()) {
			invoke += this.sanctioning[enforcer.ordinal()][SanctionAbstract.Type.INFORMATION
					.ordinal()];
		}
		
		this.normSalience.updateSalience(own, complied, violated, npv, pun, sanc,
				invoke);
		
		// Update Self-Interest
		if ((this.contract != null) && (this.contract.getSupplier() == this.id)
				&& (own != 0)) {
			double offered = this.contract.getQtyOffered() * this.sellingPrice;
			double demanded = this.contract.getQtyDemanded() * this.sellingPrice;
			double supplied = this.contract.getQtySupplied() * this.sellingPrice;
			double consumed = this.contract.getQtyConsumed() * this.sellingPrice;
			double actual = Math.max(0, (this.generateReal - this.consumeReal))
					* this.sellingPrice;
			
			if (this.contract.getComplied()) {
				if (actual != 0) {
					this.selfInterest += (actual - consumed) / actual;
				}
			} else {
				if ((offered + this.totalSanctions) != 0) {
					this.selfInterest += (supplied - demanded - this.totalSanctions)
							/ (demanded + this.totalSanctions);
				}
			}
			
			this.selfInterest = Math.max(0.0, Math.min(1.0, this.selfInterest));
		}
		
		// Norm Adoption
		if ((!this.normSalience.isActive())
				&& (this.normSalience.getSalience() >= this.NORM_ACTIVATE_SAL_THRESHOLD)
				&& (this.normativeMsg >= this.NORM_ACTIVATE_NUM_MSG)
				&& (this.normativeAction >= this.NORM_ACTIVATE_NUM_ACTION)) {
			this.normSalience.setStatus(Status.ACTIVE);
		}
		
		if ((this.normSalience.isActive())
				&& (this.normSalience.getSalience() < this.NORM_ACTIVATE_SAL_THRESHOLD)) {
			this.normativeMsg = 0;
			this.normativeAction = 0;
			this.normSalience.setStatus(Status.INACTIVE);
		}
		
		// Update ostracized list
		for(Integer prosumerId : this.prosumers.keySet()) {
			if (prosumerId != this.id) {
				if (this.ostracized.contains(prosumerId)) {
					if ((this.reputation.hasReputation(prosumerId))
							&& (this.reputation.getEvaluation(prosumerId) > this.OSTRACIZE_THRESHOLD)) {
						this.ostracized.remove(prosumerId);
						logger.debug("[ID=" + this.id + "] [READMITED=" + prosumerId
								+ "] [REPUTATION=" + this.reputation.getEvaluation(prosumerId));
					}
				} else {
					if (this.reputation.getEvaluation(prosumerId) < this.OSTRACIZE_THRESHOLD) {
						this.ostracized.add(prosumerId);
						logger
								.debug("[ID=" + this.id + "] [OSTRACIZED=" + prosumerId + "]");
					}
				}
			}
		}
	}
	
	
	@Override
	public void generateOutput() {
		AbstractEntity entity = (ProsumerSocialOutputEntity) OutputController
				.getEntity(EntityType.SOCIAL_PROSUMER);
		
		entity.setValue(Field.CYCLE.name(), this.timestep);
		entity.setValue(Field.PROSUMER_ID.name(), this.id);
		entity.setValue(Field.WEALTH.name(), this.wealth);
		entity.setValue(Field.SELF_INTEREST.name(), this.selfInterest);
		
		entity.setValue(Field.NORM_STATUS.name(), this.normSalience.getStatus()
				.name());
		entity
				.setValue(Field.NORM_SALIENCE.name(), this.normSalience.getSalience());
		
		entity.setValue(Field.CONSUME_ESTIMATION.name(), this.consumeEstimation);
		entity.setValue(Field.GENERATE_ESTIMATION.name(), this.generateEstimation);
		entity.setValue(Field.CONSUME_REAL.name(), this.consumeReal);
		entity.setValue(Field.GENERATE_REAL.name(), this.generateReal);
		entity.setValue(Field.CONSUME_ACCURACY.name(), this.consumeAccuracy);
		entity.setValue(Field.GENERATE_ACCURACY.name(), this.generateAccuracy);
		
		if (this.contract != null) {
			
			AbstractEntity contractEntity = (ContractOutputEntity) OutputController
					.getEntity(EntityType.CONTRACT, this.contract.getId());
			
			contractEntity.setValue(
					ContractOutputEntity.Field.SELLER_COMPLIED.name(),
					this.contract.getComplied());
			
			contractEntity.setActive();
			
			OutputController.setEntity(EntityType.CONTRACT, contractEntity);
			
			// Supplier
			if (this.contract.getSupplier() == this.id) {
				entity.setValue(Field.SUPPLIER.name(), true);
				entity.setValue(Field.BUYER_IMAGE.name(),
						this.reputation.getImage(this.contract.getBuyer()));
				entity.setValue(Field.BUYER_REPUTATION.name(),
						this.reputation.getReputation(this.contract.getBuyer()));
				entity.setValue(Field.BUYER.name(), false);
				entity.setValue(Field.SUPPLIER_IMAGE.name(),
						this.reputation.getImage(this.contract.getSupplier()));
				entity.setValue(Field.SUPPLIER_REPUTATION.name(),
						this.reputation.getReputation(this.contract.getSupplier()));
				entity.setValue(Field.BALANCE.name(), this.energyBalance());
				
				// Buyer
			} else if (this.contract.getBuyer() == this.id) {
				entity.setValue(Field.SUPPLIER.name(), false);
				entity.setValue(Field.BUYER_IMAGE.name(),
						this.reputation.getImage(this.contract.getBuyer()));
				entity.setValue(Field.BUYER_REPUTATION.name(),
						this.reputation.getReputation(this.contract.getBuyer()));
				entity.setValue(Field.BUYER.name(), true);
				entity.setValue(Field.SUPPLIER_IMAGE.name(),
						this.reputation.getImage(this.contract.getSupplier()));
				entity.setValue(Field.SUPPLIER_REPUTATION.name(),
						this.reputation.getReputation(this.contract.getSupplier()));
				entity.setValue(Field.BALANCE.name(), this.energyBalance());
			}
			
		} else {
			entity.setValue(Field.SUPPLIER.name(), false);
			entity.setValue(Field.BUYER_IMAGE.name(), 0.0);
			entity.setValue(Field.BUYER_REPUTATION.name(), 0.0);
			entity.setValue(Field.BUYER.name(), false);
			entity.setValue(Field.SUPPLIER_IMAGE.name(), 0.0);
			entity.setValue(Field.SUPPLIER_REPUTATION.name(), 0.0);
			entity.setValue(Field.BALANCE.name(), 0.0);
		}
		
		entity.setValue(Field.PROVIDER_BUY.name(), this.providerBuyEnergy);
		entity.setValue(Field.PROVIDER_PAID.name(), this.providerAmountPaid);
		entity.setValue(Field.PROVIDER_SELL.name(), this.providerSellEnergy);
		entity
				.setValue(Field.PROVIDER_RECEIVED.name(), this.providerAmountReceived);
		entity.setValue(Field.SANCTIONING_COST.name(), this.sanctionCost);
		
		entity.setValue(Field.OSTRACIZED.name(), this.ostracized.size());
		
		entity
				.setValue(
						Field.A_PUN_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.APPLIED.ordinal()][SanctionAbstract.Type.PUNISHMENT
								.ordinal()]);
		
		entity
				.setValue(
						Field.A_PUN_INFO_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.APPLIED.ordinal()][SanctionAbstract.Type.PUNISHMENT_INFORMATION
								.ordinal()]);
		
		entity
				.setValue(
						Field.A_INFO_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.APPLIED.ordinal()][SanctionAbstract.Type.INFORMATION
								.ordinal()]);
		
		entity
				.setValue(
						Field.A_DEN_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.APPLIED.ordinal()][SanctionAbstract.Type.DENOUNCE
								.ordinal()]);
		
		entity
				.setValue(
						Field.A_REP_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.APPLIED.ordinal()][SanctionAbstract.Type.REPUTATION
								.ordinal()]);
		
		entity
				.setValue(
						Field.R_PUN_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.RECEIVED.ordinal()][SanctionAbstract.Type.PUNISHMENT
								.ordinal()]);
		
		entity
				.setValue(
						Field.R_PUN_INFO_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.RECEIVED.ordinal()][SanctionAbstract.Type.PUNISHMENT_INFORMATION
								.ordinal()]);
		
		entity
				.setValue(
						Field.R_INFO_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.RECEIVED.ordinal()][SanctionAbstract.Type.INFORMATION
								.ordinal()]);
		
		entity
				.setValue(
						Field.R_REP_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.RECEIVED.ordinal()][SanctionAbstract.Type.REPUTATION
								.ordinal()]);
		
		entity
				.setValue(
						Field.R_SUSPENSION_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.RECEIVED.ordinal()][SanctionAbstract.Type.SUSPENSION
								.ordinal()]);
		
		entity
				.setValue(
						Field.O_PUN_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.PUNISHMENT
								.ordinal()]);
		
		entity
				.setValue(
						Field.O_PUN_INFO_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.PUNISHMENT_INFORMATION
								.ordinal()]);
		
		entity
				.setValue(
						Field.O_INFO_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.INFORMATION
								.ordinal()]);
		
		entity
				.setValue(
						Field.O_DENOUNCE_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.DENOUNCE
								.ordinal()]);
		
		entity
				.setValue(
						Field.O_SUSPENSION_SANCTION.name(),
						this.sanctioning[SanctionAbstract.Enforcer.OBSERVED.ordinal()][SanctionAbstract.Type.SUSPENSION
								.ordinal()]);
		
		entity.setActive();
		
		OutputController.setEntity(EntityType.SOCIAL_PROSUMER, entity);
	}
}